/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.core.base;

import org.proteusframework.core.api.model.INamespace;
import org.proteusframework.core.util.Assert;


/**
 * Simplest implementation of a fully qualified named object. Each fully qualified named object consists of a
 * namespace and an ID that must be guaranteed unique within the given namespace.
 * <p/>
 * Reference ID is used to distinguish between different instances of the same object. For example, two instances
 * of com.foo.Bar could be given reference IDs of Unit1 and Unit2, respectively. Reference ID <i>may</i> be null.
 *
 * @author Tacoma Four
 */
public class Namespace implements INamespace, Comparable<INamespace>
{
    /**
     * Namespace delimiter used between a Family and an ID when a canonical representation of any namespace is required.
     */
    public static final String CANONICAL_NAMESPACE_DELIM = "::";

    /**
     * Null token, represented by the literal <code>&lt;null%gt;</code>.
     */
    public static final String NULL_TOKEN = "<null>";

    /**
     * Immutable, non-null namespace.
     */
    private final String namespace;

    /**
     * Immutable, non-null ID guaranteed to be unique within the namespace.
     */
    private final String id;

    /**
     * Mutable, nullable reference ID.
     */
    protected String refId;

    /**
     * Constructs a copy of the provided qualified name.
     *
     * @param namespace Non-null namespace
     */
    public Namespace(final INamespace namespace)
    {
        Assert.parameterNotNull(namespace, "Parameter 'namespace' must not be null");

        Assert.parameterNotNull(namespace.getFamily(), "Parameter 'namespace#getFamily()' must not be null");
        Assert.parameterNotNull(namespace.getId(), "Parameter 'namespace#getId()' must not be null");

        this.namespace = namespace.getFamily();
        this.id = namespace.getId();
        this.refId = namespace.getRefId();
    }

    /**
     * Constructs a namespace based upon the package name and class name.
     *
     * @param clazz Class to extract details from
     */
    public Namespace(final Class<?> clazz)
    {
        this(clazz.getPackage().getName(), clazz.getSimpleName());
    }

    /**
     * Constructs a qualified name.
     *
     * @param namespace Non-null, immutable namespace
     * @param id        Non-null, immutable ID that is guaranteed unique within the namespace
     */
    public Namespace(final String namespace, final String id)
    {
        this(namespace, id, null);
    }

    /**
     * Constructs a qualified name.
     *
     * @param namespace Non-null, immutable namespace
     * @param id        Non-null, immutable ID that is guaranteed unique within the namespace
     * @param refId     Optional (may be null) reference ID
     */
    public Namespace(final String namespace, final String id, final String refId)
    {
        Assert.parameterNotNull(namespace, "Parameter 'namespace' must not be null");
        Assert.parameterNotNull(id, "Parameter 'id' must not be null");
        this.namespace = namespace;
        this.id = id;
        this.refId = refId;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String getFamily()
    {
        return namespace;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String getId()
    {
        return id;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String getRefId()
    {
        return refId;
    }

    public void setRefId(String refId)
    {
        this.refId = refId;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object o)
    {
        if (this == o)
        {
            return true;
        }
        if (o == null || getClass() != o.getClass())
        {
            return false;
        }

        Namespace namespace1 = (Namespace) o;

        if (!id.equals(namespace1.id))
        {
            return false;
        }
        if (!namespace.equals(namespace1.namespace))
        {
            return false;
        }
        if (refId != null ? !refId.equals(namespace1.refId) : namespace1.refId != null)
        {
            return false;
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode()
    {
        int result = namespace.hashCode();
        result = 31 * result + id.hashCode();
        result = 31 * result + (refId != null ? refId.hashCode() : 0);
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString()
    {
        return "Namespace{" +
                "namespace='" + namespace + '\'' +
                ", id='" + id + '\'' +
                ", refId='" + refId + '\'' +
                '}';
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final int compareTo(INamespace namespace)
    {
        return toCanonical(this).compareTo(toCanonical(namespace));
    }

    /**
     * Constructs a canonical representation of the namespace. This method <b>must</b> be used in lieu of calling
     * <code>toString()</code> and treating its response as a canonical representation of a namespace.
     *
     * @param namespace Namespace
     * @return Canonical representation of a namespace that is guaranteed not to change across framework releases,
     *         unlike the output of the <code>toString</code> method.
     */
    public static String toCanonical(INamespace namespace)
    {
        return toCanonical(namespace, false);
    }

    /**
     * Constructs a canonical representation of the namespace. This method <b>must</b> be used in lieu of calling
     * <code>toString()</code> and treating its response as a canonical representation of a namespace.
     *
     * @param namespace          Namespace
     * @param includeReferenceId if true, includes the reference ID in the canonical representation
     * @return Canonical representation of a namespace that is guaranteed not to change across framework releases,
     *         unlike the output of the <code>toString</code> method.
     */
    public static String toCanonical(INamespace namespace, boolean includeReferenceId)
    {
        Assert.parameterNotNull(namespace, "Parameter 'namespace' must not be null");

        String refID = "";
        if (includeReferenceId)
        {
            refID = CANONICAL_NAMESPACE_DELIM + ((namespace.getRefId() == null)
                    ? NULL_TOKEN
                    : namespace.getRefId());
        }

        return namespace.getFamily()
                + CANONICAL_NAMESPACE_DELIM
                + namespace.getId()
                + refID;
    }

    /**
     * Constructs a namespace from a canonical representation of a namespace.
     *
     * @param canonical Canonical representation
     * @return Namespace
     * @see #toCanonical(org.proteusframework.core.api.model.INamespace)
     */
    public static INamespace fromCanonical(String canonical)
    {
        Assert.parameterNotNull(canonical, "Parameter 'canonical' must not be null");

        String[] parts = canonical.split(CANONICAL_NAMESPACE_DELIM);

        String refID = null;

        if (parts.length == 3)
        {
            refID = (parts[2].equals(NULL_TOKEN) ? null : parts[2]);
        }

        return new Namespace(parts[0], parts[1], refID);
    }
}
